#include <srs_core.hpp>

#include <stdlib.h>
#include <sys/types.h>

#ifndef WIN32
#include <sys/wait.h>
#else
#include <fiber.h>
#endif

#ifdef SRS_AUTO_GPERF_MP
#include <gperftools/heap-profiler.h>
#endif
#ifdef SRS_AUTO_GPERF_CP
#include <gperftools/profiler.h>
#endif

using namespace std;

#include <srs_kernel_error.hpp>
#include <srs_app_server.hpp>
#include <srs_app_config.hpp>
#include <srs_app_log.hpp>
#include <srs_kernel_utility.hpp>
#include <srs_core_performance.hpp>
#include <srs_app_dvr.hpp>

// pre-declare
int run();
int run_master();

// app module.
SrsConfig* _srs_config = new SrsConfig();
SrsServer* _srs_server = new SrsServer();
// version of srs, which can grep keyword "XCORE"
extern const char* _srs_version;

/**
* show the features by macro, the actual macro values.
*/
void show_macro_features() {
#ifdef SRS_AUTO_SSL
    srs_trace("check feature rtmp handshake: on");
#else
    srs_warn("check feature rtmp handshake: off");
#endif

#ifdef SRS_AUTO_HLS
    srs_trace("check feature hls: on");
#else
    srs_warn("check feature hls: off");
#endif

#ifdef SRS_AUTO_HDS
    srs_trace("check feature hds: on");
#else
    srs_warn("check feature hds: off");
#endif

#ifdef SRS_AUTO_HTTP_CALLBACK
    srs_trace("check feature http callback: on");
#else
    srs_warn("check feature http callback: off");
#endif

#ifdef SRS_AUTO_HTTP_API
    srs_trace("check feature http api: on");
#else
    srs_warn("check feature http api: off");
#endif

#ifdef SRS_AUTO_HTTP_SERVER
    srs_trace("check feature http server: on");
#else
    srs_warn("check feature http server: off");
#endif

#ifdef SRS_AUTO_HTTP_CORE
    srs_trace("check feature http parser: on");
#else
    srs_warn("check feature http parser: off");
#endif

#ifdef SRS_AUTO_DVR
    srs_trace("check feature dvr: on");
#else
    srs_warn("check feature dvr: off");
#endif

#ifdef SRS_AUTO_TRANSCODE
    srs_trace("check feature transcode: on");
#else
    srs_warn("check feature transcode: off");
#endif

#ifdef SRS_AUTO_INGEST
    srs_trace("check feature ingest: on");
#else
    srs_warn("check feature ingest: off");
#endif

#ifdef SRS_AUTO_STAT
    srs_trace("check feature system stat: on");
#else
    srs_warn("check feature system stat: off");
#endif

#ifdef SRS_AUTO_NGINX
    srs_trace("check feature compile nginx: on");
#else
    srs_warn("check feature compile nginx: off");
#endif

#ifdef SRS_AUTO_FFMPEG_TOOL
    srs_trace("check feature compile ffmpeg: on");
#else
    srs_warn("check feature compile ffmpeg: off");
#endif

#ifdef SRS_AUTO_STREAM_CASTER
    srs_trace("stream caster: on");
#else
    srs_warn("stream caster: off");
#endif

#ifdef SRS_PERF_MERGED_READ
    srs_trace("MR(merged-read): on, @see %s", RTMP_SIG_SRS_ISSUES(241));
#else
    srs_warn("MR(merged-read): off, @see %s", RTMP_SIG_SRS_ISSUES(241));
#endif

    srs_trace("MR(merged-read) default %d sleep %d", SRS_PERF_MR_ENABLED, SRS_PERF_MR_SLEEP);
    srs_trace("MW(merged-write) default sleep %d", SRS_PERF_MW_SLEEP);
    srs_trace("read chunk stream cache cid [0, %d)", SRS_PERF_CHUNK_STREAM_CACHE);
    srs_trace("default gop cache %d, play queue %ds", SRS_PERF_GOP_CACHE, SRS_PERF_PLAY_QUEUE);

#ifndef SRS_PERF_COMPLEX_SEND
    srs_warn("complex send algorithm disabled.");
#else
    srs_trace("complex send algorithm enabled.");
#endif

#ifdef SRS_PERF_TCP_NODELAY
    srs_warn("TCP_NODELAY enabled, may hurts performance.");
#else
    srs_trace("TCP_NODELAY disabled.");
#endif

#ifdef SRS_PERF_SO_SNDBUF_SIZE
    srs_warn("socket send buffer size %d", SRS_PERF_SO_SNDBUF_SIZE);
#else
    srs_trace("auto guess socket send buffer by merged write");
#endif

    int possible_mr_latency = 0;
#ifdef SRS_PERF_MERGED_READ
    possible_mr_latency = SRS_PERF_MR_SLEEP;
#endif
    srs_trace("system default latency in ms: mw(0-%d) + mr(0-%d) + play-queue(0-%d)",
              SRS_PERF_MW_SLEEP, possible_mr_latency, SRS_PERF_PLAY_QUEUE * 1000);
}

void check_macro_features() {
    // important preset.
#ifdef SRS_OSX
    srs_trace("SRS for OSX");
#endif
#ifdef SRS_PI
    srs_trace("SRS for pi");
#endif
#ifdef SRS_CUBIE
    srs_trace("SRS for cubieboard");
#endif
#ifdef SRS_ARM_UBUNTU12
    srs_trace("SRS for arm(build on ubuntu)");
#endif
#ifdef SRS_MIPS_UBUNTU12
    srs_trace("SRS for mips(build on ubuntu)");
#endif

    // for special features.
#ifndef SRS_PERF_MERGED_READ
    srs_warn("MR(merged-read) is disabled, hurts read performance. @see %s", RTMP_SIG_SRS_ISSUES(241));
#endif

#ifndef WIN32
    srs_trace("writev limits write %d iovs a time", sysconf(_SC_IOV_MAX));
#endif

#if VERSION_MAJOR > VERSION_STABLE
#warning "current branch is not stable, please use stable branch instead."
    srs_warn("SRS %s is not stable, please use stable branch %s instead", RTMP_SIG_SRS_VERSION, VERSION_STABLE_BRANCH);
#endif

#ifdef SRS_AUTO_MEM_WATCH
#warning "srs memory watcher will hurts performance. user should kill by SIGTERM or init.d script."
    srs_warn("srs memory watcher will hurts performance. user should kill by SIGTERM or init.d script.");
#endif

#if defined(SRS_AUTO_STREAM_CASTER)
//#warning "stream caster is experiment feature."
    srs_warn("stream caster is experiment feature.");
#endif

#if defined(SRS_PERF_SO_SNDBUF_SIZE) && !defined(SRS_PERF_MW_SO_SNDBUF)
#error "SRS_PERF_SO_SNDBUF_SIZE depends on SRS_PERF_MW_SO_SNDBUF"
#endif

#ifndef SRS_OSX
#if defined(__amd64__)
    srs_trace("cpu is amd64, glibc %d.%d", (int)__GLIBC__, (int)__GLIBC_MINOR__);
#endif
#if defined(__x86_64__)
    srs_trace("cpu is x86_64, glibc %d.%d", (int)__GLIBC__, (int)__GLIBC_MINOR__);
#endif
#if defined(__i386__)
    srs_trace("cpu is i386, glibc %d.%d", (int)__GLIBC__, (int)__GLIBC_MINOR__);
#endif
#if defined(__arm__)
    srs_trace("cpu is arm, glibc %d.%d", (int)__GLIBC__, (int)__GLIBC_MINOR__);
#endif
#endif
}

int start(int argc, char** argv) {
    int ret = ERROR_SUCCESS;

    // TODO: support both little and big endian.
    srs_assert(srs_is_little_endian());

    // for gperf gmp or gcp,
    // should never enable it when not enabled for performance issue.
#ifdef SRS_AUTO_GPERF_MP
    HeapProfilerStart("gperf.srs.gmp");
#endif
#ifdef SRS_AUTO_GPERF_CP
    ProfilerStart("gperf.srs.gcp");
#endif

    // directly compile error when these two macro defines.
#if defined(SRS_AUTO_GPERF_MC) && defined(SRS_AUTO_GPERF_MP)
#error ("option --with-gmc confict with --with-gmp, "
    "@see: http://google-perftools.googlecode.com/svn/trunk/doc/heap_checker.html\n"
    "Note that since the heap-checker uses the heap-profiling framework internally, "
    "it is not possible to run both the heap-checker and heap profiler at the same time");
#endif

    // never use gmp to check memory leak.
#ifdef SRS_AUTO_GPERF_MP
#warning "gmp is not used for memory leak, please use gmc instead."
#endif

    // never use srs log(srs_trace, srs_error, etc) before config parse the option,
    // which will load the log config and apply it.
    if ((ret = _srs_config->parse_options(argc, argv)) != ERROR_SUCCESS) {
        srs_error("parse options failed, ret = %d", ret);
        return ret;
    }

    // change the work dir and set cwd.
    string cwd = _srs_config->get_work_dir();
    if (!cwd.empty() && cwd != "./" && (ret = chdir(cwd.c_str())) != ERROR_SUCCESS) {
        srs_error("change cwd to %s failed. ret=%d", cwd.c_str(), ret);
        return ret;
    }
    if ((ret = _srs_config->initialize_cwd()) != ERROR_SUCCESS) {
        srs_error("config initialize cwd failed, ret = %d", ret);
        return ret;
    }

    // config parsed, initialize log.
    if ((ret = _srs_log->initialize()) != ERROR_SUCCESS) {
        srs_error("log initialize failed, ret = %d", ret);
        return ret;
    }

    // we check the config when the log initialized.
    if ((ret = _srs_config->check_config()) != ERROR_SUCCESS) {
        srs_error("config check failed, ret = %d", ret);
        return ret;
    }

    srs_trace(RTMP_SIG_SRS_SERVER ", stable is " RTMP_SIG_SRS_PRIMARY);
    srs_trace("license: " RTMP_SIG_SRS_LICENSE ", " RTMP_SIG_SRS_COPYRIGHT);
    srs_trace("primary/master: " RTMP_SIG_SRS_PRIMARY);
    srs_trace("authors: " RTMP_SIG_SRS_AUTHROS);
    srs_trace("contributors: " SRS_AUTO_CONSTRIBUTORS);
    srs_trace("uname: " SRS_AUTO_UNAME);
    srs_trace("build: %s, %s", SRS_AUTO_BUILD_DATE, srs_is_little_endian() ? "little-endian" : "big-endian");
    srs_trace("configure: " SRS_AUTO_USER_CONFIGURE);
    srs_trace("features: " SRS_AUTO_CONFIGURE);
#ifdef SRS_AUTO_ARM_UBUNTU12
    srs_trace("arm tool chain: " SRS_AUTO_EMBEDED_TOOL_CHAIN);
#endif
    srs_trace("conf: %s, limit: %d", _srs_config->config().c_str(), _srs_config->get_max_connections());

    // features
    check_macro_features();
    show_macro_features();

    /**
    * we do nothing in the constructor of server,
    * and use initialize to create members, set hooks for instance the reload handler,
    * all initialize will done in this stage.
    */
    if ((ret = _srs_server->initialize(NULL)) != ERROR_SUCCESS) {
        srs_error("server initialize failed, ret = %d", ret);
        return ret;
    }

    return run();
}

void* run_master_st(void*);

int run() {

    // if not deamon, directly run master.
    if (_srs_config->get_deamon() == true) {
        srs_trace("start deamon mode...");

#ifndef WIN32
        int pid = fork();
        if (pid < 0) {
            srs_error("create process error. ret=-1"); //ret=0
            return -1;
        }

        // grandpa
        if (pid > 0) {
            int status = 0;
            if (waitpid(pid, &status, 0) == -1) {
                srs_error("wait child process error! ret=-1"); //ret=0
            }
            srs_trace("grandpa process exit.");
            exit(0);
        }

        // father
        pid = fork();
        if (pid < 0) {
            srs_error("create process error. ret=0");
            return -1;
        }

        if (pid > 0) {
            srs_trace("father process exit. ret=0");
            exit(0);
        }
#endif
        // son
        srs_trace("son(deamon) process running.");
    }

    int ret = ERROR_SUCCESS;
    if ((ret = _srs_server->initialize_st()) != ERROR_SUCCESS) {
        srs_error("initialize_st failed, ret = %d", ret);
        return ret;
    }

#ifndef WIN32
    return run_master();
#else
    auto sg = createFiberSG();

    st_thread_create(run_master_st, NULL, 0, 0);

    while (st_active_count() > 0) {
        st_usleep(1000);
    }

    delFiberSG(sg);
    return 0;
#endif
}

void* run_master_st(void* ptr) {
    run_master();
    return NULL;
}

int run_master() {
    int ret = ERROR_SUCCESS;

#ifdef WIN32
    socket_setup();
#endif

    initSrsDvrPostFile(_srs_config->get_post_file_process());

    if ((ret = _srs_server->initialize_signal()) != ERROR_SUCCESS) {
        srs_error("initialize_signal failed, ret = %d", ret);
        return ret;
    }

    if ((ret = _srs_server->acquire_pid_file()) != ERROR_SUCCESS) {
        srs_error("acquire_pid_file failed, ret = %d", ret);
        return ret;
    }

    if ((ret = _srs_server->listen()) != ERROR_SUCCESS) {
        srs_error("listen failed, ret = %d", ret);
        return ret;
    }

    if ((ret = _srs_server->register_signal()) != ERROR_SUCCESS) {
        srs_error("register_signal failed, ret = %d", ret);
        return ret;
    }

    if ((ret = _srs_server->http_handle()) != ERROR_SUCCESS) {
        srs_error("http_handle failed, ret = %d", ret);
        return ret;
    }

    if ((ret = _srs_server->ingest()) != ERROR_SUCCESS) {
        srs_error("ingest failed, ret = %d", ret);
        return ret;
    }

    if ((ret = _srs_server->cycle()) != ERROR_SUCCESS) {
        srs_error("cycle failed, ret = %d", ret);
        return ret;
    }

#ifdef WIN32
    socket_cleanup();
#endif

    return 0;
}

/**
* main entrance.
*/
int main(int argc, char** argv) {
    SrsFastLog log;
    initISrsLog(&log);
    initISrsThread(nullptr);

    auto ret = start(argc, argv);
    if (ret != ERROR_SUCCESS) {
        srs_error("start failed, ret = %d", ret);
        exit(0);
    }
    return 0;
}